﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;

namespace GBricks.Collections
{
  [Serializable]
  [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = Justifications.FakeEnumerableForCollectionInitializer)]
  public sealed class HashCode : IEnumerable<object>
  {
    private const int NullHashCode = 0;

    public int Value { get; private set; }
    public int Count { get; private set; }

    [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "places-1", Justification = Justifications.ByDesign)]
    [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "32-places", Justification = Justifications.ByDesign)]
    internal static int RotateRight(int hashCode, int places) {
      if((places &= 0x1F) == 0) {
        return hashCode;
      }//if

      var mask = ~0x7FFFFFFF >> unchecked(places - 1);
      return ((hashCode >> places) & ~mask) | ((hashCode << unchecked(32 - places)) & mask);
    }

    #region Combine

    public static int Combine(int hash1, int hash2) {
      return hash1 ^ RotateRight(hash2, 1);
    }

    public static int Combine(int hash1, int hash2, int hash3) {
      return Combine(hash1, hash2) ^ RotateRight(hash3, 2);
    }

    [SuppressMessage("Microsoft.Design", "CA1025:ReplaceRepetitiveArgumentsWithParamsArray", Justification = Justifications.ByDesign)]
    public static int Combine(int hash1, int hash2, int hash3, int hash4) {
      return Combine(hash1, hash2, hash3) ^ RotateRight(hash4, 3);
    }

    [SuppressMessage("Microsoft.Design", "CA1025:ReplaceRepetitiveArgumentsWithParamsArray", Justification = Justifications.ByDesign)]
    public static int Combine(int hash1, int hash2, int hash3, int hash4, int hash5) {
      return Combine(hash1, hash2, hash3, hash4) ^ RotateRight(hash5, 4);
    }

    [SuppressMessage("Microsoft.Design", "CA1025:ReplaceRepetitiveArgumentsWithParamsArray", Justification = Justifications.ByDesign)]
    public static int Combine(int hash1, int hash2, int hash3, int hash4, int hash5, int hash6) {
      return Combine(hash1, hash2, hash3, hash4, hash5) ^ RotateRight(hash6, 5);
    }

    [SuppressMessage("Microsoft.Design", "CA1025:ReplaceRepetitiveArgumentsWithParamsArray", Justification = Justifications.ByDesign)]
    public static int Combine(int hash1, int hash2, int hash3, int hash4, int hash5, int hash6, int hash7) {
      return Combine(hash1, hash2, hash3, hash4, hash5, hash6) ^ RotateRight(hash7, 6);
    }

    [SuppressMessage("Microsoft.Design", "CA1025:ReplaceRepetitiveArgumentsWithParamsArray", Justification = Justifications.ByDesign)]
    public static int Combine(int hash1, int hash2, int hash3, int hash4, int hash5, int hash6, int hash7, int hash8) {
      return Combine(hash1, hash2, hash3, hash4, hash5, hash6, hash7) ^ RotateRight(hash8, 7);
    }

    #endregion Combine

    #region Calculate

    public static int Calculate<T>(T value) {
      return value != null ? value.GetHashCode() : NullHashCode;
    }

    public static int Calculate<T1, T2>(T1 value1, T2 value2) {
      return Combine(Calculate(value1), Calculate(value2));
    }

    public static int Calculate<T1, T2, T3>(T1 value1, T2 value2, T3 value3) {
      return Combine(Calculate(value1), Calculate(value2), Calculate(value3));
    }

    public static int Calculate<T1, T2, T3, T4>(T1 value1, T2 value2, T3 value3, T4 value4) {
      return Combine(Calculate(value1), Calculate(value2), Calculate(value3), Calculate(value4));
    }

    public static int Calculate<T1, T2, T3, T4, T5>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) {
      return Combine(Calculate(value1), Calculate(value2), Calculate(value3), Calculate(value4), Calculate(value5));
    }

    public static int Calculate<T1, T2, T3, T4, T5, T6>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) {
      return Combine(Calculate(value1), Calculate(value2), Calculate(value3), Calculate(value4), Calculate(value5), Calculate(value6));
    }

    public static int Calculate<T1, T2, T3, T4, T5, T6, T7>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) {
      return Combine(Calculate(value1), Calculate(value2), Calculate(value3), Calculate(value4), Calculate(value5), Calculate(value6), Calculate(value7));
    }

    public static int Calculate<T1, T2, T3, T4, T5, T6, T7, T8>(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) {
      return Combine(Calculate(value1), Calculate(value2), Calculate(value3), Calculate(value4), Calculate(value5), Calculate(value6), Calculate(value7), Calculate(value8));
    }

    public static int CalculateWith<T>(T value, IEqualityComparer<T> comparer) {
      return value != null ? (comparer != null ? comparer.GetHashCode(value) : value.GetHashCode()) : NullHashCode;
    }

    public static int CalculateWith<T1, T2>(T1 value1, IEqualityComparer<T1> comparer1, T2 value2, IEqualityComparer<T2> comparer2) {
      return Combine(CalculateWith(value1, comparer1), CalculateWith(value2, comparer2));
    }

    public static int CalculateWith<T>(T value, Func<T, int> provider) {
      return value != null ? (provider != null ? provider(value) : value.GetHashCode()) : NullHashCode;
    }

    public static int CalculateWith<T>(T value, Func<T, int, int> provider, int index) {
      return value != null ? (provider != null ? provider(value, index) : value.GetHashCode()) : NullHashCode;
    }

    #endregion Calculate

    #region CalculateAll

    public static int CalculateAll<T>(IEnumerable<T> items, IEqualityComparer<T> comparer = null) {
      return new HashCode { { items, comparer } }.Value;
    }

    public static int CalculateAll<T>(IEnumerable<T> items, Func<T, int> provider) {
      return new HashCode { { items, provider } }.Value;
    }

    public static int CalculateAll<T>(IEnumerable<T> items, Func<T, int, int> provider) {
      return new HashCode { { items, provider } }.Value;
    }

    #endregion CalculateAll

    #region Add

    public HashCode Add<T>(T value, IEqualityComparer<T> comparer = null) {
      Contract.Ensures(Contract.Result<HashCode>() != null);
      var hash = CalculateWith(value, comparer);
      return AddHash(hash);
    }

    public HashCode Add<T>(T value, Func<T, int> provider) {
      Contract.Ensures(Contract.Result<HashCode>() != null);
      var hash = CalculateWith(value, provider);
      return AddHash(hash);
    }

    public HashCode Add<T>(T value, Func<T, int, int> provider) {
      Contract.Ensures(Contract.Result<HashCode>() != null);
      var hash = CalculateWith(value, provider, Count);
      return AddHash(hash);
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public HashCode Add<T>(IEnumerable<T> value, IEqualityComparer<T> comparer = null) {
      Contract.Ensures(Contract.Result<HashCode>() != null);

      if(value == null) {
        return this;
      }//if

      return value.Aggregate(this, (acc, item) => acc.Add(item, comparer));
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public HashCode Add<T>(IEnumerable<T> value, Func<T, int> provider) {
      Contract.Ensures(Contract.Result<HashCode>() != null);

      if(value == null) {
        return this;
      }//if

      return value.Aggregate(this, (acc, item) => acc.Add(item, provider));
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public HashCode Add<T>(IEnumerable<T> value, Func<T, int, int> provider) {
      Contract.Ensures(Contract.Result<HashCode>() != null);

      if(value == null) {
        return this;
      }//if

      return value.Aggregate(this, (acc, item) => acc.Add(item, provider));
    }

    #endregion Add

    public HashCode AddHash(int hashCode) {
      Contract.Ensures(Contract.Result<HashCode>() != null);
      Count++;
      Value ^= RotateRight(hashCode, Count);
      return this;
    }

    public override string ToString() {
      Contract.Ensures(Contract.Result<string>() != null);
      return Value.ToString(CultureInfo.InvariantCulture);
    }

    #region IEnumerable<object> Members

    IEnumerator<object> IEnumerable<object>.GetEnumerator() {
      Contract.Ensures(Contract.Result<IEnumerator<object>>() != null);
      return Enumerable.Empty<object>().GetEnumerator();
    }

    #endregion IEnumerable<object> Members

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
      Contract.Ensures(Contract.Result<System.Collections.IEnumerator>() != null);
      return Enumerable.Empty<object>().GetEnumerator();
    }

    #endregion IEnumerable Members
  }
}
